/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.sip.communicator.impl.gui.main.call;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import net.java.sip.communicator.impl.gui.main.contactlist.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.contactsource.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.account.*;
import net.java.sip.communicator.util.skin.*;

/**
 * The <tt>ChooseCallAccountDialog</tt> is the dialog shown when calling a
 * contact in order to let the user choose the account he'd prefer to use in
 * order to call this contact.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class ChooseCallAccountPopupMenu
    extends SIPCommPopupMenu
    implements Skinnable
{
    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 0L;

    /**
     * The invoker component.
     */
    protected final JComponent invoker;

    /**
     * The call interface listener, which would be notified once the call
     * interface is created.
     */
    private CallInterfaceListener callInterfaceListener;

    /**
     * The <tt>MetaContact</tt> we're calling.
     */
    private UIContactImpl uiContact;

    /**
     * Creates this dialog.
     *
     * @param invoker the invoker of this pop up menu
     * @param contactToCall the contact to call
     * @param telephonyProviders a list of all possible telephony providers
     */
    public ChooseCallAccountPopupMenu(
        JComponent invoker,
        final String contactToCall,
        List<ProtocolProviderService> telephonyProviders)
    {
        this(invoker, contactToCall, telephonyProviders,
            OperationSetBasicTelephony.class);
    }

    /**
     * Creates this dialog.
     *
     * @param invoker the invoker of this pop up menu
     * @param contactToCall the contact to call
     * @param telephonyProviders a list of all possible telephony providers
     * @param l <tt>CallInterfaceListener</tt> instance
     */
    public ChooseCallAccountPopupMenu(
        JComponent invoker,
        final String contactToCall,
        List<ProtocolProviderService> telephonyProviders,
        CallInterfaceListener l)
    {
        this(invoker, contactToCall, telephonyProviders,
            OperationSetBasicTelephony.class);

        callInterfaceListener = l;
    }

    /**
     * Creates this dialog.
     *
     * @param invoker the invoker of this pop up menu
     * @param contactToCall the contact to call
     * @param telephonyProviders a list of all possible telephony providers
     * @param opSetClass the operation set class indicating what operation
     * would be performed when a given item is selected from the menu
     */
    public ChooseCallAccountPopupMenu(
        JComponent invoker,
        final String contactToCall,
        List<ProtocolProviderService> telephonyProviders,
        Class<? extends OperationSet> opSetClass)
    {
        this.invoker = invoker;
        this.init(GuiActivator.getResources()
                    .getI18NString(getI18NKeyCallVia()));

        for (ProtocolProviderService provider : telephonyProviders)
        {
            this.addTelephonyProviderItem(provider, contactToCall, opSetClass);
        }
    }

    /**
     * Creates this dialog by specifying a list of telephony contacts to choose
     * from.
     *
     * @param invoker the invoker of this pop up
     * @param telephonyObjects the list of telephony contacts to select through
     */
    public ChooseCallAccountPopupMenu(  JComponent invoker,
                                        List<?> telephonyObjects)
    {
        this(   invoker,
                telephonyObjects,
                OperationSetBasicTelephony.class);
    }

    /**
     * Creates this dialog by specifying a list of telephony contacts to choose
     * from.
     *
     * @param invoker the invoker of this pop up
     * @param telephonyObjects the list of telephony contacts to select through
     * @param opSetClass the operation class, which indicates what action would
     * be performed if an item is selected from the list
     */
    public ChooseCallAccountPopupMenu(JComponent invoker,
                                      List<?> telephonyObjects,
                                      Class<? extends OperationSet> opSetClass)
    {
        this.invoker = invoker;
        this.init(GuiActivator.getResources()
                    .getI18NString(getI18NKeyChooseContact()));

        for (Object o : telephonyObjects)
        {
            if (o instanceof UIContactDetailImpl)
                this.addTelephonyContactItem(
                    (UIContactDetailImpl) o, opSetClass);
            else if (o instanceof ChatTransport)
                this.addTelephonyChatTransportItem((ChatTransport) o,
                        opSetClass);
        }
    }

    /**
     * Returns the key to use for choose contact string. Can be overridden
     * by extenders.
     * @return the key to use for choose contact string.
     */
    protected String getI18NKeyChooseContact()
    {
        return "service.gui.CHOOSE_CONTACT";
    }

    /**
     * Returns the key to use for choose contact string. Can be overridden
     * by extenders.
     * @return the key to use for choose contact string.
     */
    protected String getI18NKeyCallVia()
    {
        return "service.gui.CALL_VIA";
    }

    /**
     * Initializes and add some common components.
     *
     * @param infoString the string we'd like to show on the top of this
     * popup menu
     */
    private void init(String infoString)
    {
        setInvoker(invoker);

        this.add(createInfoLabel(infoString));

        this.addSeparator();

        this.setFocusable(true);
    }

    /**
     * Adds the given <tt>telephonyProvider</tt> to the list of available
     * telephony providers.
     *
     * @param telephonyProvider the provider to add.
     * @param contactString the contact to call when the provider is selected
     * @param opSetClass the operation set class indicating what action would
     * be performed when an item is selected
     */
    private void addTelephonyProviderItem(
        final ProtocolProviderService telephonyProvider,
        final String contactString,
        final Class<? extends OperationSet> opSetClass)
    {
        final ProviderMenuItem providerItem
            = new ProviderMenuItem(telephonyProvider);

        providerItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                if (uiContact != null)
                    itemSelected(
                        opSetClass,
                        providerItem.getProtocolProvider(),
                        contactString,
                        uiContact);
                else
                    itemSelected(
                        opSetClass,
                        providerItem.getProtocolProvider(),
                        contactString);

                if (callInterfaceListener != null)
                    callInterfaceListener.callInterfaceStarted();

                ChooseCallAccountPopupMenu.this.setVisible(false);
            }
        });

        this.add(providerItem);
    }

    /**
     * Adds the given <tt>telephonyContact</tt> to the list of available
     * telephony contact.
     *
     * @param telephonyContact the telephony contact to add
     * @param opSetClass the operation set class, that indicates the action that
     * would be performed when an item is selected
     */
    private void addTelephonyContactItem(
        final UIContactDetailImpl telephonyContact,
        final Class<? extends OperationSet> opSetClass)
    {
        final ContactMenuItem contactItem
            = new ContactMenuItem(telephonyContact);

        contactItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                List<ProtocolProviderService> providers
                    = AccountUtils.getOpSetRegisteredProviders(
                        opSetClass,
                        telephonyContact.getPreferredProtocolProvider(opSetClass),
                        telephonyContact.getPreferredProtocol(opSetClass));

                if (providers == null || providers.size() <= 0)
                {
                    new ErrorDialog(null,
                        GuiActivator.getResources().getI18NString(
                            "service.gui.CALL_FAILED"),
                        GuiActivator.getResources().getI18NString(
                            "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
                        .showDialog();
                    return;
                }
                else if (providers.size() > 1)
                {
                    itemSelected(
                        opSetClass, providers, telephonyContact.getAddress());
                }
                else // providersCount == 1
                {
                    ProtocolProviderService provider = providers.get(0);
                    String contactAddress = telephonyContact.getAddress();

                    if (uiContact != null)
                        itemSelected(
                            opSetClass,
                            provider,
                            contactAddress,
                            uiContact);
                    else
                        itemSelected(
                            opSetClass,
                            provider,
                            contactAddress);
                }

                ChooseCallAccountPopupMenu.this.setVisible(false);
            }
        });

        String category = telephonyContact.getCategory();

        if (category != null && category.equals(ContactDetail.Category.Phone))
        {
            int index = findPhoneItemIndex();
            if (index < 0)
                add(contactItem);
            else
                insert(contactItem, findPhoneItemIndex());
        }
        else
        {
            Component lastComp = getComponent(getComponentCount() - 1);
            if (lastComp instanceof ContactMenuItem)
                category = ((ContactMenuItem) lastComp).getCategory();

            if (category != null
                && category.equals(ContactDetail.Category.Phone))
                addSeparator();

            add(contactItem);
        }
    }

    /**
     * Returns the index of a phone menu item.
     *
     * @return the index of a phone menu item
     */
    private int findPhoneItemIndex()
    {
        int index = -1;
        for (int i = getComponentCount() - 1; i > 1; i--)
        {
            Component c = getComponent(i);

            if (c instanceof ContactMenuItem)
            {
                String category = ((ContactMenuItem) c).getCategory();
                if (category == null
                    || !category.equals(ContactDetail.Category.Phone))
                continue;
            }
            else if (c instanceof JSeparator)
                index = i - 1;
            else
                return index;
        }

        return index;
    }

    /**
     * Adds the given <tt>ChatTransport</tt> to the list of available
     * telephony chat transports.
     *
     * @param telTransport the telephony chat transport to add
     * @param opSetClass the class of the operation set indicating the operation
     * to be executed in the item is selected
     */
    private void addTelephonyChatTransportItem(
        final ChatTransport telTransport,
        final Class<? extends OperationSet> opSetClass)
    {
        final ChatTransportMenuItem transportItem
            = new ChatTransportMenuItem(telTransport);

        transportItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                ProtocolProviderService provider
                    = telTransport.getProtocolProvider();
                String contactAddress = telTransport.getName();

                if (uiContact != null)
                    CallManager.createCall(
                        opSetClass, provider, contactAddress, uiContact);
                else
                    CallManager.createCall(
                        opSetClass, provider, contactAddress);

                ChooseCallAccountPopupMenu.this.setVisible(false);
            }
        });

        this.add(transportItem);
    }

    /**
     * Shows the dialog at the given location.
     *
     * @param x the x coordinate
     * @param y the y coordinate
     */
    public void showPopupMenu(int x, int y)
    {
        setLocation(x, y);
        setVisible(true);
    }

    /**
     * Shows this popup menu regarding to its invoker location.
     */
    public void showPopupMenu()
    {
        Point location = new Point(invoker.getX(),
            invoker.getY() + invoker.getHeight());

        SwingUtilities
            .convertPointToScreen(location, invoker.getParent());
        setLocation(location);
        setVisible(true);
    }

    /**
     * Sets the <tt>UIContactImpl</tt> we're currently calling.
     *
     * @param uiContact the <tt>UIContactImpl</tt> we're currently calling
     */
    public void setUIContact(UIContactImpl uiContact)
    {
        this.uiContact = uiContact;
    }

    /**
     * Creates the info label.
     *
     * @param infoString the string we'd like to show on the top of this
     * popup menu
     * @return the created info label
     */
    private Component createInfoLabel(String infoString)
    {
        JMenuItem infoLabel = new JMenuItem();

        infoLabel.setEnabled(false);
        infoLabel.setFocusable(false);

        infoLabel.setText("<html><b>" + infoString + "</b></html>");

        return infoLabel;
    }

    /**
     * Item was selected, give a chance for extenders to override.
     *
     * @param opSetClass the operation set to use.
     * @param protocolProviderService the protocol provider
     * @param contact the contact address
     *  @param uiContact the <tt>MetaContact</tt> selected
     */
    protected void itemSelected(
                    Class<? extends OperationSet> opSetClass,
                    ProtocolProviderService protocolProviderService,
                    String contact,
                    UIContactImpl uiContact)
    {
        CallManager.createCall(
            opSetClass,
            protocolProviderService,
            contact,
            uiContact);
    }

    /**
     * Item was selected, give a chance for extenders to override.
     *
     * @param opSetClass the operation set to use.
     * @param protocolProviderService the protocol provider
     * @param contact the contact address selected
     */
    protected void itemSelected(Class<? extends OperationSet> opSetClass,
                    ProtocolProviderService protocolProviderService,
                    String contact)
    {
        CallManager.createCall(
            opSetClass,
            protocolProviderService,
            contact);
    }

    /**
     * Item was selected, give a chance for extenders to override.
     *
     * @param opSetClass the operation set to use.
     * @param providers list of available protocol providers
     * @param contact the contact address selected
     */
    protected void itemSelected(Class<? extends OperationSet> opSetClass,
                                List<ProtocolProviderService> providers,
                                String contact)
    {
        ChooseCallAccountDialog callAccountDialog
            = new ChooseCallAccountDialog(contact, opSetClass, providers);

        if (uiContact != null)
            callAccountDialog.setUIContact(uiContact);
        callAccountDialog.setVisible(true);
    }

    /**
     * A custom menu item corresponding to a specific
     * <tt>ProtocolProviderService</tt>.
     */
    private class ProviderMenuItem
        extends JMenuItem
        implements Skinnable
    {
        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 0L;

        private final ProtocolProviderService protocolProvider;

        public ProviderMenuItem(ProtocolProviderService protocolProvider)
        {
            this.protocolProvider = protocolProvider;
            this.setText(protocolProvider.getAccountID().getDisplayName());

            loadSkin();
        }

        public ProtocolProviderService getProtocolProvider()
        {
            return protocolProvider;
        }

        /**
         * Reloads protocol icon.
         */
        public void loadSkin()
        {
            byte[] protocolIcon
                = protocolProvider.getProtocolIcon()
                    .getIcon(ProtocolIcon.ICON_SIZE_16x16);

            if (protocolIcon != null)
                this.setIcon(ImageLoader.getIndexedProtocolIcon(
                                ImageUtils.getBytesInImage(protocolIcon),
                                protocolProvider));
        }
    }

    /**
     * A custom menu item corresponding to a specific protocol <tt>Contact</tt>.
     */
    private class ContactMenuItem
        extends JMenuItem
        implements Skinnable
    {
        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 0L;

        private final UIContactDetailImpl contact;

        public ContactMenuItem(UIContactDetailImpl contact)
        {
            this.contact = contact;

            String itemName = "<html>";
            Iterator<String> labels = contact.getLabels();

            if (labels != null && labels.hasNext())
                while (labels.hasNext())
                    itemName += "<b style=\"color: gray\">"
                                + labels.next().toLowerCase() + "</b> ";

            itemName += contact.getAddress() + "</html>";

            this.setText(itemName);
            loadSkin();
        }

        /**
         * Returns the category of the underlying contact detail.
         *
         * @return the category of the underlying contact detail
         */
        public String getCategory()
        {
            return contact.getCategory();
        }

        /**
         * Reloads contact icon.
         */
        public void loadSkin()
        {
            ImageIcon contactIcon = contact.getStatusIcon();

            if (contactIcon == null)
            {
                PresenceStatus status = contact.getPresenceStatus();

                BufferedImage statusIcon = null;
                if (status != null)
                    statusIcon = Constants.getStatusIcon(status);

                if (statusIcon != null)
                    contactIcon = ImageLoader.getIndexedProtocolIcon(
                        statusIcon,
                        contact.getPreferredProtocolProvider(null));
            }

            if (contactIcon != null)
                this.setIcon(ImageLoader.getIndexedProtocolIcon(
                    contactIcon.getImage(),
                    contact.getPreferredProtocolProvider(null)));
        }
    }

    /**
     * A custom menu item corresponding to a specific <tt>ChatTransport</tt>.
     */
    private class ChatTransportMenuItem
        extends JMenuItem
        implements Skinnable
    {
        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 0L;

        private final ChatTransport chatTransport;

        public ChatTransportMenuItem(ChatTransport chatTransport)
        {
            this.chatTransport = chatTransport;
            this.setText(chatTransport.getName());

            loadSkin();
        }

        /**
         * Reloads transport icon.
         */
        public void loadSkin()
        {
            PresenceStatus status = chatTransport.getStatus();
            byte[] statusIconBytes = status.getStatusIcon();

            Icon statusIcon = null;
            if (statusIconBytes != null && statusIconBytes.length > 0)
            {
                statusIcon = ImageLoader.getIndexedProtocolIcon(
                    ImageUtils.getBytesInImage(statusIconBytes),
                    chatTransport.getProtocolProvider());
            }

            if (statusIcon != null)
                this.setIcon(statusIcon);
        }
    }

    /**
     * Reloads all menu items.
     */
    public void loadSkin()
    {
        Component[] components = getComponents();
        for(Component component : components)
        {
            if(component instanceof Skinnable)
            {
                Skinnable skinnableComponent = (Skinnable) component;
                skinnableComponent.loadSkin();
            }
        }
    }
}
